//Copyright (C) 2007 James Ashley (www.imaginativeuniversal.com) under the terms of the LGPL.
using System;
using System.Collections.Generic;
using System.Speech.Recognition;
using System.Text;
using System.IO;


namespace SophiaBot
{
    public class GameEngineBot: BotServerBase
    {
        #region local fields

        protected SAPIZMachine _zMachine = null;
        protected string _filePath = string.Empty;
        protected string _savedGamesFolderPath = string.Empty;

        #endregion

        #region constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="GameEngineBot"/> class.
        /// </summary>
        public GameEngineBot()
        {
           _name = "Game Engine";
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GameEngineBot"/> class.
        /// </summary>
        /// <param name="savedGamesFolderPath">The saved games folder path.</param>
        public GameEngineBot(string savedGamesFolderPath): this()
        {
            _savedGamesFolderPath = savedGamesFolderPath;
        }

        #endregion

        #region IBotServer Members

        /// <summary>
        /// Reads the specified game input.
        /// </summary>
        /// <param name="inputText">The input text.</param>
        public override void Read(string inputText)
        {
            if (_isRunning)
            {
                string response = _zMachine.GetOutput(inputText).Replace(">", "").Trim();
                if ((inputText.ToLower() == "y" || inputText.ToLower() == "yes") && response == string.Empty)
                    response = "bye.";

                RaiseBotResponseEvent(this, response);
            }
            else
            {
                if(null != _nextBot)
                    _nextBot.Read(inputText);
            }
        }

        /// <summary>
        /// Gets game specific grammar.
        /// </summary>
        /// <returns></returns>
        public override Grammar[] GetGrammars()
        {
            Grammar g = GetGameGrammar();
            g.Name = this.Name;
            g.Priority = this._priority;
            g.SpeechRecognized += new EventHandler<SpeechRecognizedEventArgs>(GameGrammar_SpeechRecognized);
            return new Grammar[1] { g };
        }


        /// <summary>
        /// Gets the game status information.
        /// </summary>
        /// <returns></returns>
        public override string GetStatus()
        {
            if (null != _zMachine)
                return _zMachine.GetStatus();
            else
            {
                string g = _filePath;
                if (_filePath.IndexOf(".") > -1)
                {
                    g = _filePath.Substring(0, _filePath.LastIndexOf("."));
                }
                return g + " selected";
            }
        }

        /// <summary>
        /// Starts a new game.
        /// </summary>
        public override void Start()
        {
            _zMachine = new SAPIZMachine();
            _zMachine.OnRestart = new ZMachine.VirtualMachine.RestartHandler(_zMachine_OnRestart);
            _zMachine.OnSerialize += new SAPIZMachine.SerializeHandler(_zMachine_OnSerialize);
            _zMachine.OnDeserialize += new SAPIZMachine.DeserializeHandler(_zMachine_OnDeserialize);
            _zMachine.OnQuit += new SAPIZMachine.OnQuitHandler(_zMachine_GameOver);

            if (!LoadFile())
                return;
            base.Start();
            RaiseBotInfoResponseEvent(this, _zMachine.GetOutput(null).Replace(">", "").Trim());
        }

        /// <summary>
        /// Starts a new game at the specified file path.
        /// </summary>
        /// <param name="filePath">The file path.</param>
        public override void Start(string filePath)
        {
            _filePath = filePath;
            this.Start();
        }

        /// <summary>
        /// Stops this instance.
        /// </summary>
        public override void Stop()
        {
            if (null != _zMachine)
                _zMachine.Stop();
        }

        #endregion

        #region zmachine delegate handlers

        /// <summary>
        ///  On restart.
        /// </summary>
        private void _zMachine_OnRestart()
        {
            LoadFile();
            RaiseBotInfoResponseEvent(this, _zMachine.GetOutput(null));
        }

        /// <summary>
        /// On serialize.
        /// </summary>
        /// <param name="token">The token.</param>
        /// <param name="objectBytes">The object bytes.</param>
        /// <param name="statusXML">The status XML.</param>
        protected virtual void _zMachine_OnSerialize(Guid token, byte[] objectBytes, string statusXML)
        {
            //use params to save file
            FileStream fStream = new FileStream(SavedGamesFolderPath + CurrentFileName + "save.dat", FileMode.CreateNew);
            BinaryWriter bw = new BinaryWriter(fStream);
            bw.Write(objectBytes);
            bw.Close();
            fStream.Close();  
        }

        /// <summary>
        /// On deserialize.
        /// </summary>
        /// <param name="token">The token.</param>
        /// <returns></returns>
        private byte[] _zMachine_OnDeserialize(Guid token)
        {
            //find file from guid and return byte array
            string filepath = SavedGamesFolderPath + CurrentFileName + "save.dat";
            if(System.IO.File.Exists(filepath))
            {
                System.IO.FileStream fs = new System.IO.FileStream(filepath, System.IO.FileMode.Open);
                byte[] storyBytes = new byte[fs.Length];
                fs.Read(storyBytes, 0, (int)fs.Length);
                fs.Close();
                return storyBytes;
            }
            else
            {
                return new byte[0];
            }
        }

        /// <summary>
        /// Game over.
        /// </summary>
        protected void _zMachine_GameOver()
        {
            base.Stop();
        }

        #endregion

        #region helper properties and methods

        /// <summary>
        /// Loads the file.
        /// </summary>
        /// <returns></returns>
        protected virtual bool LoadFile()
        {
            System.IO.FileStream fs = new System.IO.FileStream(_filePath, System.IO.FileMode.Open);


            byte[] storyBytes = new byte[fs.Length];
            fs.Read(storyBytes, 0, (int)fs.Length);
            fs.Close();

            try
            {
                _zMachine.Create(storyBytes);
            }
            catch (Exception ex)
            {
                RaiseBotInfoResponseEvent(this, ex.Message);
                return false;
            }

            return true;
        }

        /// <summary>
        /// Gets the game grammar.
        /// </summary>
        /// <returns></returns>
        protected virtual Grammar GetGameGrammar()
        {
            Choices choices = new Choices();
            Choices secondChoices = new Choices();
            GrammarBuilder before;
            GrammarBuilder after;
            GrammarBuilder twoWordGrammar;
            foreach (string keyword in GameLexicon.GetAllItems())
            {
                //can't use in grammar
                if (keyword.IndexOf("\"") > -1)
                    continue;

                string KEYWORD = keyword.ToUpper();
                //wildcard before keyword
                before = new GrammarBuilder();
                before.AppendDictation();
                before.Append(KEYWORD);
                //wildcard after keyword
                after = new GrammarBuilder();
                after.Append(KEYWORD);
                after.AppendDictation();

                choices.Add(before);
                choices.Add(after);
                choices.Add(KEYWORD);
                secondChoices.Add(KEYWORD);
            }
            foreach (string firstKeyword in GameLexicon.GetAllItems())
            {
                //can't use in grammar
                if (firstKeyword.IndexOf("\"") > -1)
                    continue;

                string FIRSTKEYWORD = firstKeyword.ToUpper();
                twoWordGrammar = new GrammarBuilder();
                twoWordGrammar.Append(FIRSTKEYWORD);
                twoWordGrammar.Append(secondChoices);
                choices.Add(twoWordGrammar);
            }
            Grammar g = new Grammar(new GrammarBuilder(choices));
            return g;
        }

        /// <summary>
        /// Gets the game lexicon.
        /// </summary>
        /// <value>The game lexicon.</value>
        protected ZMachine.ZDictionary GameLexicon
        {
            get { return _zMachine.GameLexicon; }
        }

        #endregion
        
        #region public properties and methods

        /// <summary>
        /// Gets or sets the saved games folder path.
        /// </summary>
        /// <value>The saved games folder path.</value>
        public string SavedGamesFolderPath
        {
            get { return _savedGamesFolderPath; }
            set { _savedGamesFolderPath = value; }
        }

        /// <summary>
        /// Gets the name of the current file.
        /// </summary>
        /// <value>The name of the current file.</value>
        public string CurrentFileName
        {
            get
            {
                string filename = _filePath;
                filename = filename.Substring(0, filename.Length - 4);
                filename = filename.Substring(filename.LastIndexOf("\\") + 1);
                return filename;
            }
        }

        /// <summary>
        /// Handles the SpeechRecognized event of the GameGrammar object.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Speech.Recognition.SpeechRecognizedEventArgs"/> instance containing the event data.</param>
        public virtual void GameGrammar_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            string resultText = e.Result.Text;
            RaiseTextRecognizedEvent(resultText);
            DebugWriteToConsole(resultText + " Recognized in GameEngine bot Grammar");
            resultText = FormatProperCase(resultText);
            Read(resultText);
        }

        /// <summary>
        /// Lists the game keywords.
        /// </summary>
        /// <returns></returns>
        public string ListGameKeywords()
        {
            if (IsRunning)
            {

                StringBuilder sb = new StringBuilder();
                foreach (string word in GameLexicon.GetAllItems())
                {
                    sb.Append(", " + word);
                }
                if (sb.Length > 2)
                    return sb.ToString().Substring(2);
                else
                    return "No keywords were found.";

            }
            else
            {
                return "No game has been loaded.";
            }
        }

        #endregion
    }
}
